package scaladuct;

import net.sf.cglib.beans.BeanGenerator;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassWriter;
import scala.Function1;

import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

/**
 * <p/>
 * Created by IntelliJ IDEA.
 * User: zslajchrt
 * Date: Aug 16, 2010
 * Time: 5:06:32 PM
 */
public class ExpressCookie implements Serializable {

    private static final long serialVersionUID = -8884485776349527L;

    private byte[] ownByteCode = null;

    private static class SerializableWrapper implements Serializable {

        private static final long serialVersionUID = -5207590034778249667L;

        private final String className;
        private byte[] cookieByteCode = null;
        private Map<String, Object> properties;

        private SerializableWrapper(String className, byte[] cookieByteCode, Map<String, Object> properties) {
            this.className = className;
            this.cookieByteCode = cookieByteCode;
            this.properties = properties;
        }

        protected Object readResolve() throws ObjectStreamException {
            try {
                ClassLoader myCL = new OwnClassLoader();

                Class cookieClass = myCL.loadClass(className);
                Object cookie = cookieClass.newInstance();
                ExpressCookie.initializeCookie(cookie, cookieByteCode);
                BeanUtils.updateBean(properties, cookie);

                return cookie;
            } catch (Exception e) {
                throw new IllegalStateException("Cannot load cookie class " + className, e);
            }
        }

        private class OwnClassLoader extends ClassLoader {
            public OwnClassLoader() {
                super(Thread.currentThread().getContextClassLoader());
            }

            @Override
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                if (name.equals(className)) {
                    return defineClass(className, cookieByteCode, 0, cookieByteCode.length);
                } else {
                    return null;
                }
            }

            @Override
            protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
                // First, check if the class has already been loaded
                Class c = findLoadedClass(name);
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    c = findClass(name);
                    if (c == null) {
                        try {
                            c = getParent().loadClass(name);
                        } catch (ClassNotFoundException e) {
                            // ClassNotFoundException thrown if class not found
                            // from the non-null parent class loader
                        }
                    }
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }

        }
    }

    protected Object writeReplace() throws ObjectStreamException {
        BeanInfo beanInfo = BeanUtils.createBeanInfo(this);
        return new SerializableWrapper(getClass().getName(), ownByteCode, new HashMap(beanInfo.asJavaMap()));
    }

    public static void initializeCookie(Object cookie, byte[] byteCode) {
        ((ExpressCookie) cookie).ownByteCode = byteCode;
    }

    public static <T> T generateCookie(Class<T> superClass, Function1<BeanGenerator, BeanGenerator> propertyAdder) {
        try {
            BeanGenerator cookieGenerator = new BeanGenerator();
            cookieGenerator.setSuperclass(superClass);

            cookieGenerator = propertyAdder.apply(cookieGenerator);

            cookieGenerator.setUseCache(false);
            cookieGenerator.createClass();
            ClassWriter classWriter = new ClassWriter(1);

            final String[] className = new String[1];
            cookieGenerator.generateClass(new ClassAdapter(classWriter) {
                public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
                    className[0] = name.replace('/', '.');
                    super.visit(version, access, name, signature, superName, interfaces);
                }
            });

            final byte[] byteCode = classWriter.toByteArray();
            ClassLoader classLoader = new ClassLoader(Thread.currentThread().getContextClassLoader()) {
                protected Class findClass(String name) {
                    if (name.equals(className[0]))
                        return defineClass(name, byteCode, 0, byteCode.length);
                    else return null;
                }
            };
            Class cls = classLoader.loadClass(className[0]);
            Object cookie = cls.newInstance();

            ExpressCookie.initializeCookie(cookie, byteCode);

            return (T)cookie;

        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }

    }
}
